Loading required package: lattice
Loading required package: survival
Loading required package: Formula
Loading required package: ggplot2

Attaching package: ‘Hmisc’

The following objects are masked from ‘package:base’:

    format.pval, units

Warning messages:
1: In if (charToRaw(x) < 20) paste("\\u", toupper(format(as.hexmode(as.integer(charToRaw(x))),  :
  the condition has length > 1 and only the first element will be used
2: In if (charToRaw(x) < 20) paste("\\u", toupper(format(as.hexmode(as.integer(charToRaw(x))),  :
  the condition has length > 1 and only the first element will be used
3: In if (charToRaw(x) < 20) paste("\\u", toupper(format(as.hexmode(as.integer(charToRaw(x))),  :
  the condition has length > 1 and only the first element will be used
4: In if (charToRaw(x) < 20) paste("\\u", toupper(format(as.hexmode(as.integer(charToRaw(x))),  :
  the condition has length > 1 and only the first element will be used
5: In if (charToRaw(x) < 20) paste("\\u", toupper(format(as.hexmode(as.integer(charToRaw(x))),  :
  the condition has length > 1 and only the first element will be used
6: In if (charToRaw(x) < 20) paste("\\u", toupper(format(as.hexmode(as.integer(charToRaw(x))),  :
  the condition has length > 1 and only the first element will be used
7: In if (charToRaw(x) < 20) paste("\\u", toupper(format(as.hexmode(as.integer(charToRaw(x))),  :
  the condition has length > 1 and only the first element will be used

Attaching package: ‘dplyr’

The following objects are masked from ‘package:Hmisc’:

    src, summarize

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union


======================
Welcome to d3heatmap version 0.9.0

Type citation('d3heatmap') for how to cite the package.
Type ?d3heatmap for the main documentation.

The github page is: https://github.com/talgalili/d3heatmap/
Please submit your suggestions and bug-reports at: https://github.com/talgalili/d3heatmap/issues
You may ask questions at stackoverflow, use the r and d3heatmap tags: 
     https://stackoverflow.com/questions/tagged/d3heatmap
======================


Attaching package: ‘d3heatmap’

The following objects are masked from ‘package:base’:

    print, save


Attaching package: ‘lubridate’

The following objects are masked from ‘package:base’:

    date, intersect, setdiff, union

Introduction

The purpose of this notebook is to give data locations, data ingestion code, and code for rudimentary analysis and visualization of COVID-19 data provided by New York Times, [NYT1].

The following steps are taken:

Note that other, older repositories with COVID-19 data exist, like, [JH1, VK1].

Remark: The time series section is done for illustration purposes only. The forecasts there should not be taken seriously.

Preliminary defintions

From the help of tolower:

capwords <- function(s, strict = FALSE) {
    cap <- function(s) paste(toupper(substring(s, 1, 1)),
                  {s <- substring(s, 2); if(strict) tolower(s) else s},
                             sep = "", collapse = " " )
    sapply(strsplit(s,  split = " "), cap, USE.NAMES = !is.null(names(s)))
}

Import data

NYTimes USA states data

if( !exists("dfNYDataStates") ) {
  dfNYDataStates <- read.csv( "https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-states.csv", 
                              colClasses = c("character", "character", "character", "integer", "integer"), 
                              stringsAsFactors = FALSE )
  colnames(dfNYDataStates) <- capwords(colnames(dfNYDataStates))
}
head(dfNYDataStates)
dfNYDataStates$DateObject <- as.POSIXct(dfNYDataStates$Date)
summary(as.data.frame(unclass(dfNYDataStates), stringsAsFactors = TRUE))
         Date                 State            Fips           Cases             Deaths        DateObject                 
 2021-09-22:   56   Washington   :  653   53     :  653   Min.   :      1   Min.   :    0   Min.   :2020-01-21 00:00:00  
 2021-09-23:   56   Illinois     :  650   17     :  650   1st Qu.:  19940   1st Qu.:  420   1st Qu.:2020-08-02 00:00:00  
 2021-09-24:   56   California   :  649   06     :  649   Median : 124641   Median : 2288   Median :2021-01-02 00:00:00  
 2021-09-25:   56   Arizona      :  648   04     :  648   Mean   : 359504   Mean   : 6678   Mean   :2021-01-02 01:00:00  
 2021-09-26:   56   Massachusetts:  642   25     :  642   3rd Qu.: 453738   3rd Qu.: 7937   3rd Qu.:2021-06-04 00:00:00  
 2021-09-27:   56   Wisconsin    :  638   55     :  638   Max.   :4943059   Max.   :72471   Max.   :2021-11-03 00:00:00  
 (Other)   :33326   (Other)      :29782   (Other):29782                                                                  

Summary by state:

by( data = as.data.frame(unclass(dfNYDataStates)), INDICES = dfNYDataStates$State, FUN = summary )

Alternative summary:

Hmisc::describe(dfNYDataStates)

NYTimes USA counties data

if(!exists("dfNYDataCounties") ) {
  dfNYDataCounties <- read.csv( "https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-counties.csv", 
                                colClasses = c("character", "character", "character", "character", "integer", "integer"),
                                stringsAsFactors = FALSE )
  colnames(dfNYDataCounties) <- capwords(colnames(dfNYDataCounties))
}
head(dfNYDataCounties)
dfNYDataCounties$DateObject <- as.POSIXct(dfNYDataCounties$Date)
summary(as.data.frame(unclass(dfNYDataCounties), stringsAsFactors = TRUE))
         Date                County             State              Fips             Cases             Deaths          DateObject                 
 2021-09-03:   3251   Washington:  18294   Texas   : 144980          :  17199   Min.   :      0   Min.   :    0.0   Min.   :2020-01-21 00:00:00  
 2021-10-11:   3251   Unknown   :  15495   Georgia :  94329   53061  :    653   1st Qu.:    207   1st Qu.:    3.0   1st Qu.:2020-08-26 00:00:00  
 2021-04-05:   3250   Jefferson :  15325   Virginia:  77719   17031  :    650   Median :   1116   Median :   21.0   Median :2021-01-18 00:00:00  
 2021-08-03:   3250   Franklin  :  14682   Kentucky:  69652   06059  :    649   Mean   :   6432   Mean   :  122.2   Mean   :2021-01-16 23:56:00  
 2021-08-04:   3250   Jackson   :  13983   Missouri:  67253   04013  :    648   3rd Qu.:   3784   3rd Qu.:   72.0   3rd Qu.:2021-06-12 00:00:00  
 2021-08-10:   3250   Lincoln   :  13945   Illinois:  59829   06037  :    648   Max.   :1497297   Max.   :34601.0   Max.   :2021-11-03 00:00:00  
 (Other)   :1862094   (Other)   :1789872   (Other) :1367834   (Other):1861149                     NA's   :42707                                  

US county records

if(!exists("dfUSACountyData")){
  dfUSACountyData <- read.csv( "https://raw.githubusercontent.com/antononcube/SystemModeling/master/Data/dfUSACountyRecords.csv", 
                               colClasses = c("character", "character", "character", "character", "integer", "numeric", "numeric"),
                               stringsAsFactors = FALSE )
}
head(dfUSACountyData)
summary(as.data.frame(unclass(dfUSACountyData), stringsAsFactors = TRUE))
         Country          State                   County          FIPS        Population            Lat             Lon         
 UnitedStates:3143   Texas   : 254   WashingtonCounty:  30   01001  :   1   Min.   :      89   Min.   :19.60   Min.   :-166.90  
                     Georgia : 159   JeffersonCounty :  25   01003  :   1   1st Qu.:   10980   1st Qu.:34.70   1st Qu.: -98.23  
                     Virginia: 134   FranklinCounty  :  24   01005  :   1   Median :   25690   Median :38.37   Median : -90.40  
                     Kentucky: 120   JacksonCounty   :  23   01007  :   1   Mean   :  102248   Mean   :38.46   Mean   : -92.28  
                     Missouri: 115   LincolnCounty   :  23   01009  :   1   3rd Qu.:   67507   3rd Qu.:41.81   3rd Qu.: -83.43  
                     Kansas  : 105   MadisonCounty   :  19   01011  :   1   Max.   :10170292   Max.   :69.30   Max.   : -67.63  
                     (Other) :2256   (Other)         :2999   (Other):3137                                                       

Merge data

dsNYDataCountiesExtended <- 
  dfNYDataCounties %>% 
  dplyr::inner_join( dfUSACountyData %>% dplyr::select_at( .vars = c("FIPS", "Lat", "Lon", "Population") ), by = c( "Fips" = "FIPS" ) )
dsNYDataCountiesExtended

Basic data analysis

ParetoPlotForColumns( as.data.frame(lapply(dsNYDataCountiesExtended[,c("Cases", "Deaths")], as.numeric)), c("Cases", "Deaths"), scales = "free" )

Geo-histogram

ggplot2

Note that in the plots in this sub-section we filter out Hawaii and Alaska.

ggplot2::ggplot(dsNYDataCountiesExtended[ dsNYDataCountiesExtended$Lon > -130, c("Lat", "Lon", "Cases")]) +
  ggplot2::geom_point( ggplot2::aes(x = Lon, y = Lat, fill = log10(Cases)), alpha = 0.01, size = 0.5, color = "blue" ) + 
  ggplot2::coord_quickmap()

Leaflet

The most recent versions of leaflet RStudio are having problems with the visualization below.

cf <- colorBin( palette = "Reds", domain = log10(dsNYDataCountiesExtended$Cases), bins = 10 )
m <- 
  leaflet( dsNYDataCountiesExtended[, c("Lat", "Lon", "Cases")] ) %>%
  addTiles() %>% 
  addCircleMarkers( ~Lon, ~Lat, radius = ~ log10(Cases), fillColor = ~ cf(log10(Cases)), color = ~ cf(log10(Cases)), fillOpacity = 0.8, stroke = FALSE, popup = ~Cases )
m
dsNYDataCountiesExtended

Heat-map plots

An alternative of the geo-visualization is to use a heat-map plot.

Cases

Make a heat-map plot by sorting the rows of the cross-tabulation matrix (that correspond to states):

matSDC <- xtabs( Cases ~ State + Date, dfNYDataStates, sparse = TRUE)
d3heatmap::d3heatmap( log10(matSDC+1), cellnote = as.matrix(matSDC), scale = "none", dendrogram = "row", colors = "Blues") #, theme = "dark")
Warning in RColorBrewer::brewer.pal(n, pal) :
  n too large, allowed maximum for palette RdYlBu is 11
Returning the palette you asked for with that many colors

Warning in RColorBrewer::brewer.pal(n, pal) :
  n too large, allowed maximum for palette RdYlBu is 11
Returning the palette you asked for with that many colors

Deaths

Cross-tabulate states with dates over deaths and plot:

matSDD <- xtabs( Deaths ~ State + Date, dfNYDataStates, sparse = TRUE)
d3heatmap::d3heatmap( log10(matSDD+1), cellnote = as.matrix(matSDD), scale = "none", dendrogram = "row", colors = "Blues") #, theme = "dark")
Warning in RColorBrewer::brewer.pal(n, pal) :
  n too large, allowed maximum for palette RdYlBu is 11
Returning the palette you asked for with that many colors

Warning in RColorBrewer::brewer.pal(n, pal) :
  n too large, allowed maximum for palette RdYlBu is 11
Returning the palette you asked for with that many colors

Time series analysis

In this section we do simple “forecasting” (not a serious attempt).

Make time series data frame in long form:

dfQuery <- 
  dfNYDataStates %>% 
  dplyr::group_by( Date, DateObject ) %>% 
  dplyr::summarise_at( .vars = c("Cases", "Deaths"), .funs = sum )
dfQueryLongForm <- tidyr::pivot_longer( dfQuery, cols = c("Cases", "Deaths"), names_to = "Variable", values_to = "Value")
head(dfQueryLongForm)

Plot the time series:

ggplot(dfQueryLongForm) +
  geom_line( aes( x = DateObject, y = log10(Value) ) ) +
  facet_wrap( ~Variable, ncol = 1 )

Cases

Fit using ARIMA:

fit <- forecast::auto.arima( dfQuery$Cases )
fit
Series: dfQuery$Cases 
ARIMA(2,2,2) 

Coefficients:
         ar1     ar2      ma1     ma2
      0.8073  -0.452  -1.6198  0.8419
s.e.  0.0406   0.047   0.0221  0.0251

sigma^2 estimated as 725101702:  log likelihood=-7563.74
AIC=15137.49   AICc=15137.58   BIC=15159.88

Plot “forecast”:

plot( forecast::forecast(fit, h = 20) )
grid(nx = NULL, ny = NULL, col = "lightgray", lty = "dotted")

Deaths

Fit with ARIMA:

fit <- forecast::auto.arima( dfQuery$Deaths )
fit
Series: dfQuery$Deaths 
ARIMA(3,2,2) 

Coefficients:
         ar1      ar2      ar3      ma1     ma2
      0.9694  -0.6119  -0.1969  -1.4248  0.6841
s.e.  0.0495   0.0545   0.0477   0.0368  0.0302

sigma^2 estimated as 171619:  log likelihood=-4845.9
AIC=9703.81   AICc=9703.94   BIC=9730.68

Plot “forecast”:

plot( forecast::forecast(fit, h = 20) )
grid(nx = NULL, ny = NULL, col = "lightgray", lty = "dotted")

Fluctuations

We want to see does the time series data have fluctuations around its trends and estimate the distributions of those fluctuations. (Knowing those distributions some further studies can be done.)

This can be efficiently using the software monad QRMon, [AAp2, AA1]. Here we load the QRMon package:

#devtools::install_github(repo = "antononcube/QRMon-R")
library(QRMon)
Warning: replacing previous import ‘magrittr::set_names’ by ‘purrr::set_names’ when loading ‘QRMon’

Fluctuations presence

Here we plot the consecutive differences of the cases and deaths:

dfQueryLongForm <- 
  dfQueryLongForm %>% 
  dplyr::group_by( Variable ) %>% 
  dplyr::arrange( DateObject ) %>% 
  dplyr::mutate( Difference = c(0, diff(Value) ) ) %>% 
  dplyr::ungroup()
ggplot(dfQueryLongForm) +
  geom_line( aes( x = DateObject, y = Difference ) ) +
  facet_wrap( ~Variable, ncol = 1, scales = "free_y" )

From the plots we see that time series are not monotonically increasing, and there are non-trivial fluctuations in the data.

Absolute and relative errors distributions

Here we take interesting part of the cases data:

dfQueryLongForm2 <- 
  dfQueryLongForm %>% 
  dplyr::filter( difftime( DateObject, as.POSIXct("2020-05-01")) >= 0 ) %>% 
  dplyr::mutate( Regressor = as.numeric(DateObject, origin = "1900-01-01") )

Here we specify a QRMon workflow that rescales the data, fits a B-spline curve to get the trend, and finds the absolute and relative errors (residuals, fluctuations) around that trend:

qrObj <- 
  QRMonUnit(dfQueryLongForm2 %>% dplyr::filter( Variable == "Cases") %>% dplyr::select( Regressor, Value) ) %>% 
  QRMonRescale(regressorAxisQ = F, valueAxisQ = T) %>% 
  QRMonEchoDataSummary %>% 
  QRMonQuantileRegression( df = 16, probabilities = 0.5 )
$Dimensions
[1] 552   2

$Summary
   Regressor             Value       
 Min.   :1.588e+09   Min.   :0.0000  
 1st Qu.:1.600e+09   1st Qu.:0.1227  
 Median :1.612e+09   Median :0.5582  
 Mean   :1.612e+09   Mean   :0.4606  
 3rd Qu.:1.624e+09   3rd Qu.:0.7180  
 Max.   :1.636e+09   Max.   :1.0000  

Here we plot the fit:

qrObj <- qrObj %>% QRMonPlot(datePlotQ = T)

Here we plot absolute errors:

qrObj <- qrObj %>% QRMonErrorsPlot(relativeErrorsQ = F, datePlotQ = T)

Here is the summary:

summary( (qrObj %>% QRMonErrors(relativeErrorsQ = F) %>% QRMonTakeValue)[[1]]$Error )
      Min.    1st Qu.     Median       Mean    3rd Qu.       Max. 
-0.0046392 -0.0007593  0.0000000  0.0001071  0.0007600  0.0082391 

Here we plot relative errors:

qrObj <- qrObj %>% QRMonErrorsPlot(relativeErrorsQ = T, datePlotQ = T)

Here is the summary:

summary( (qrObj %>% QRMonErrors(relativeErrorsQ = T) %>% QRMonTakeValue)[[1]]$Error )
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
-2.445265 -0.002165  0.000000 -0.005389  0.002182  0.169684 

Refereces

[NYT1] The New York Times, Coronavirus (Covid-19) Data in the United States, (2020), GitHub.

[WRI1] Wolfram Research Inc., USA county records, (2020), System Modeling at GitHub.

[JH1] CSSE at Johns Hopkins University, COVID-19, (2020), GitHub.

[VK1] Vitaliy Kaurov, Resources For Novel Coronavirus COVID-19, (2020), community.wolfram.com.

[AA1] Anton Antonov, “A monad for Quantile Regression workflows”, (2018), at MathematicaForPrediction WordPress.

[AAp1] Anton Antonov, Heatmap plot Mathematica package, (2018), MathematicaForPrediciton at GitHub.

[AAp2] Anton Antonov, Monadic Quantile Regression Mathematica package, (2018), MathematicaForPrediciton at GitHub.

LS0tCnRpdGxlOiAiTmV3IFlvcmsgVGltZXMgQ09WSUQtMTkgZGF0YSB2aXN1YWxpemF0aW9uIgphdXRob3I6IEFudG9uIEFudG9ub3YKZGF0ZTogMjAyMC0wMy0zMApvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICBmaWdfd2lkdGg6IDYKICAgIGZpZ19oaWdodDogNAogICAgZmlnX2FsaWduOiAiY2VudGVyIgotLS0KCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Ci5tYWluLWNvbnRhaW5lciB7CiAgbWF4LXdpZHRoOiAxODAwcHg7CiAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgbWFyZ2luLXJpZ2h0OiBhdXRvOwp9Cjwvc3R5bGU+CgpgYGB7ciwgZWNobz1GQUxTRX0KbGlicmFyeShIbWlzYykKbGlicmFyeShoaWdoY2hhcnRlcikKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGxlYWZsZXQpCmxpYnJhcnkoZDNoZWF0bWFwKQpsaWJyYXJ5KFBhcmV0b1ByaW5jaXBsZUFkaGVyZW5jZSkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoZm9yZWNhc3QpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KClRoZSBwdXJwb3NlIG9mIHRoaXMgbm90ZWJvb2sgaXMgdG8gZ2l2ZSBkYXRhIGxvY2F0aW9ucywgZGF0YSBpbmdlc3Rpb24gY29kZSwgYW5kIGNvZGUgZm9yIHJ1ZGltZW50YXJ5IGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uIG9mIENPVklELTE5IGRhdGEgcHJvdmlkZWQgYnkgTmV3IFlvcmsgVGltZXMsIFtOWVQxXS4gCgpUaGUgZm9sbG93aW5nIHN0ZXBzIGFyZSB0YWtlbjoKCi0gSW5nZXN0IGRhdGEKCiAgLSBUYWtlIENPVklELTE5IGRhdGEgZnJvbSBUaGUgTmV3IFlvcmsgVGltZXMsIGJhc2VkIG9uIHJlcG9ydHMgZnJvbSBzdGF0ZSBhbmQgbG9jYWwgaGVhbHRoIGFnZW5jaWVzLCBbTllUMV0uCgogIC0gVGFrZSBVU0EgY291bnRpZXMgcmVjb3JkcyBkYXRhIChGSVBTIGNvZGVzLCBnZW8tY29vcmRpbmF0ZXMsIHBvcHVsYXRpb25zKSwgW1dSSTFdLgoKLSBNZXJnZSB0aGUgZGF0YS4KCi0gTWFrZSBkYXRhIHN1bW1hcmllcyBhbmQgcmVsYXRlZCBwbG90cy4KCi0gTWFrZSBjb3JyZXNwb25kaW5nIGdlby1wbG90cy4KCi0gRG8g4oCcb3V0IG9mIHRoZSBib3jigJ0gdGltZSBzZXJpZXMgZm9yZWNhc3QuCgotIEFuYWx5emUgZmx1Y3R1YXRpb25zIGFyb3VuZCB0aW1lIHNlcmllcyB0cmVuZHMuCgpOb3RlIHRoYXQgb3RoZXIsIG9sZGVyIHJlcG9zaXRvcmllcyB3aXRoIENPVklELTE5IGRhdGEgZXhpc3QsIGxpa2UsIFtKSDEsIFZLMV0uCgoqKlJlbWFyazoqKiBUaGUgdGltZSBzZXJpZXMgc2VjdGlvbiBpcyBkb25lIGZvciBpbGx1c3RyYXRpb24gcHVycG9zZXMgb25seS4gVGhlIGZvcmVjYXN0cyB0aGVyZSBzaG91bGQgbm90IGJlIHRha2VuIHNlcmlvdXNseS4KCiMgUHJlbGltaW5hcnkgZGVmaW50aW9ucwoKRnJvbSB0aGUgaGVscCBvZiBgdG9sb3dlcmA6CgpgYGB7cn0KY2Fwd29yZHMgPC0gZnVuY3Rpb24ocywgc3RyaWN0ID0gRkFMU0UpIHsKICAgIGNhcCA8LSBmdW5jdGlvbihzKSBwYXN0ZSh0b3VwcGVyKHN1YnN0cmluZyhzLCAxLCAxKSksCiAgICAgICAgICAgICAgICAgIHtzIDwtIHN1YnN0cmluZyhzLCAyKTsgaWYoc3RyaWN0KSB0b2xvd2VyKHMpIGVsc2Ugc30sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIsIGNvbGxhcHNlID0gIiAiICkKICAgIHNhcHBseShzdHJzcGxpdChzLCAgc3BsaXQgPSAiICIpLCBjYXAsIFVTRS5OQU1FUyA9ICFpcy5udWxsKG5hbWVzKHMpKSkKfQpgYGAKCiMgSW1wb3J0IGRhdGEKCiMjIE5ZVGltZXMgVVNBIHN0YXRlcyBkYXRhCgpgYGB7cn0KaWYoICFleGlzdHMoImRmTllEYXRhU3RhdGVzIikgKSB7CiAgZGZOWURhdGFTdGF0ZXMgPC0gcmVhZC5jc3YoICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbnl0aW1lcy9jb3ZpZC0xOS1kYXRhL21hc3Rlci91cy1zdGF0ZXMuY3N2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbENsYXNzZXMgPSBjKCJjaGFyYWN0ZXIiLCAiY2hhcmFjdGVyIiwgImNoYXJhY3RlciIsICJpbnRlZ2VyIiwgImludGVnZXIiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSApCiAgY29sbmFtZXMoZGZOWURhdGFTdGF0ZXMpIDwtIGNhcHdvcmRzKGNvbG5hbWVzKGRmTllEYXRhU3RhdGVzKSkKfQpoZWFkKGRmTllEYXRhU3RhdGVzKQpgYGAKCmBgYHtyfQpkZk5ZRGF0YVN0YXRlcyREYXRlT2JqZWN0IDwtIGFzLlBPU0lYY3QoZGZOWURhdGFTdGF0ZXMkRGF0ZSkKYGBgCgpgYGB7cn0Kc3VtbWFyeShhcy5kYXRhLmZyYW1lKHVuY2xhc3MoZGZOWURhdGFTdGF0ZXMpLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkpCmBgYAoKU3VtbWFyeSBieSBzdGF0ZToKCmBgYHtyLCBldmFsPUZBTFNFfQpieSggZGF0YSA9IGFzLmRhdGEuZnJhbWUodW5jbGFzcyhkZk5ZRGF0YVN0YXRlcykpLCBJTkRJQ0VTID0gZGZOWURhdGFTdGF0ZXMkU3RhdGUsIEZVTiA9IHN1bW1hcnkgKQpgYGAKCkFsdGVybmF0aXZlIHN1bW1hcnk6CgpgYGB7ciwgZXZhbD1GQUxTRX0KSG1pc2M6OmRlc2NyaWJlKGRmTllEYXRhU3RhdGVzKQpgYGAKCgojIyBOWVRpbWVzIFVTQSBjb3VudGllcyBkYXRhCgpgYGB7cn0KaWYoIWV4aXN0cygiZGZOWURhdGFDb3VudGllcyIpICkgewogIGRmTllEYXRhQ291bnRpZXMgPC0gcmVhZC5jc3YoICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbnl0aW1lcy9jb3ZpZC0xOS1kYXRhL21hc3Rlci91cy1jb3VudGllcy5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xDbGFzc2VzID0gYygiY2hhcmFjdGVyIiwgImNoYXJhY3RlciIsICJjaGFyYWN0ZXIiLCAiY2hhcmFjdGVyIiwgImludGVnZXIiLCAiaW50ZWdlciIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSApCiAgY29sbmFtZXMoZGZOWURhdGFDb3VudGllcykgPC0gY2Fwd29yZHMoY29sbmFtZXMoZGZOWURhdGFDb3VudGllcykpCn0KaGVhZChkZk5ZRGF0YUNvdW50aWVzKQpgYGAKCmBgYHtyfQpkZk5ZRGF0YUNvdW50aWVzJERhdGVPYmplY3QgPC0gYXMuUE9TSVhjdChkZk5ZRGF0YUNvdW50aWVzJERhdGUpCmBgYAoKYGBge3J9CnN1bW1hcnkoYXMuZGF0YS5mcmFtZSh1bmNsYXNzKGRmTllEYXRhQ291bnRpZXMpLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkpCmBgYAoKIyMgVVMgY291bnR5IHJlY29yZHMKCmBgYHtyfQppZighZXhpc3RzKCJkZlVTQUNvdW50eURhdGEiKSl7CiAgZGZVU0FDb3VudHlEYXRhIDwtIHJlYWQuY3N2KCAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2FudG9ub25jdWJlL1N5c3RlbU1vZGVsaW5nL21hc3Rlci9EYXRhL2RmVVNBQ291bnR5UmVjb3Jkcy5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbENsYXNzZXMgPSBjKCJjaGFyYWN0ZXIiLCAiY2hhcmFjdGVyIiwgImNoYXJhY3RlciIsICJjaGFyYWN0ZXIiLCAiaW50ZWdlciIsICJudW1lcmljIiwgIm51bWVyaWMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSApCn0KaGVhZChkZlVTQUNvdW50eURhdGEpCmBgYAoKYGBge3J9CnN1bW1hcnkoYXMuZGF0YS5mcmFtZSh1bmNsYXNzKGRmVVNBQ291bnR5RGF0YSksIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFKSkKYGBgCgojIE1lcmdlIGRhdGEKCmBgYHtyfQpkc05ZRGF0YUNvdW50aWVzRXh0ZW5kZWQgPC0gCiAgZGZOWURhdGFDb3VudGllcyAlPiUgCiAgZHBseXI6OmlubmVyX2pvaW4oIGRmVVNBQ291bnR5RGF0YSAlPiUgZHBseXI6OnNlbGVjdF9hdCggLnZhcnMgPSBjKCJGSVBTIiwgIkxhdCIsICJMb24iLCAiUG9wdWxhdGlvbiIpICksIGJ5ID0gYyggIkZpcHMiID0gIkZJUFMiICkgKQpkc05ZRGF0YUNvdW50aWVzRXh0ZW5kZWQKYGBgCgoKIyBCYXNpYyBkYXRhIGFuYWx5c2lzCgpgYGB7cn0KUGFyZXRvUGxvdEZvckNvbHVtbnMoIGFzLmRhdGEuZnJhbWUobGFwcGx5KGRzTllEYXRhQ291bnRpZXNFeHRlbmRlZFssYygiQ2FzZXMiLCAiRGVhdGhzIildLCBhcy5udW1lcmljKSksIGMoIkNhc2VzIiwgIkRlYXRocyIpLCBzY2FsZXMgPSAiZnJlZSIgKQpgYGAKCiMgR2VvLWhpc3RvZ3JhbQoKIyMgZ2dwbG90MgoKTm90ZSB0aGF0IGluIHRoZSBwbG90cyBpbiB0aGlzIHN1Yi1zZWN0aW9uIHdlIGZpbHRlciBvdXQgSGF3YWlpIGFuZCBBbGFza2EuCgpgYGB7cn0KZ2dwbG90Mjo6Z2dwbG90KGRzTllEYXRhQ291bnRpZXNFeHRlbmRlZFsgZHNOWURhdGFDb3VudGllc0V4dGVuZGVkJExvbiA+IC0xMzAsIGMoIkxhdCIsICJMb24iLCAiQ2FzZXMiKV0pICsKICBnZ3Bsb3QyOjpnZW9tX3BvaW50KCBnZ3Bsb3QyOjphZXMoeCA9IExvbiwgeSA9IExhdCwgZmlsbCA9IGxvZzEwKENhc2VzKSksIGFscGhhID0gMC4wMSwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiYmx1ZSIgKSArIAogIGdncGxvdDI6OmNvb3JkX3F1aWNrbWFwKCkKYGBgCgojIyBMZWFmbGV0CgoqKipUaGUgbW9zdCByZWNlbnQgdmVyc2lvbnMgb2YgYGxlYWZsZXRgIFJTdHVkaW8gYXJlIGhhdmluZyBwcm9ibGVtcyB3aXRoIHRoZSB2aXN1YWxpemF0aW9uIGJlbG93LioqKgogCmBgYHtyfQpjZiA8LSBjb2xvckJpbiggcGFsZXR0ZSA9ICJSZWRzIiwgZG9tYWluID0gbG9nMTAoZHNOWURhdGFDb3VudGllc0V4dGVuZGVkJENhc2VzKSwgYmlucyA9IDEwICkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KbSA8LSAKICBsZWFmbGV0KCBkc05ZRGF0YUNvdW50aWVzRXh0ZW5kZWRbLCBjKCJMYXQiLCAiTG9uIiwgIkNhc2VzIildICkgJT4lCiAgYWRkVGlsZXMoKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2VycyggfkxvbiwgfkxhdCwgcmFkaXVzID0gfiBsb2cxMChDYXNlcyksIGZpbGxDb2xvciA9IH4gY2YobG9nMTAoQ2FzZXMpKSwgY29sb3IgPSB+IGNmKGxvZzEwKENhc2VzKSksIGZpbGxPcGFjaXR5ID0gMC44LCBzdHJva2UgPSBGQUxTRSwgcG9wdXAgPSB+Q2FzZXMgKQptCmBgYAoKYGBge3J9CmRzTllEYXRhQ291bnRpZXNFeHRlbmRlZApgYGAKCiMgSGVhdC1tYXAgcGxvdHMKCkFuIGFsdGVybmF0aXZlIG9mIHRoZSBnZW8tdmlzdWFsaXphdGlvbiBpcyB0byB1c2UgYSBoZWF0LW1hcCBwbG90LgoKCiMjIENhc2VzCgpNYWtlIGEgaGVhdC1tYXAgcGxvdCBieSBzb3J0aW5nIHRoZSByb3dzIG9mIHRoZSBjcm9zcy10YWJ1bGF0aW9uIG1hdHJpeCAodGhhdCBjb3JyZXNwb25kIHRvIHN0YXRlcyk6CgpgYGB7cn0KbWF0U0RDIDwtIHh0YWJzKCBDYXNlcyB+IFN0YXRlICsgRGF0ZSwgZGZOWURhdGFTdGF0ZXMsIHNwYXJzZSA9IFRSVUUpCmQzaGVhdG1hcDo6ZDNoZWF0bWFwKCBsb2cxMChtYXRTREMrMSksIGNlbGxub3RlID0gYXMubWF0cml4KG1hdFNEQyksIHNjYWxlID0gIm5vbmUiLCBkZW5kcm9ncmFtID0gInJvdyIsIGNvbG9ycyA9ICJCbHVlcyIpICMsIHRoZW1lID0gImRhcmsiKQpgYGAKCgojIyBEZWF0aHMKCkNyb3NzLXRhYnVsYXRlIHN0YXRlcyB3aXRoIGRhdGVzIG92ZXIgZGVhdGhzIGFuZCBwbG90OgoKCmBgYHtyfQptYXRTREQgPC0geHRhYnMoIERlYXRocyB+IFN0YXRlICsgRGF0ZSwgZGZOWURhdGFTdGF0ZXMsIHNwYXJzZSA9IFRSVUUpCmQzaGVhdG1hcDo6ZDNoZWF0bWFwKCBsb2cxMChtYXRTREQrMSksIGNlbGxub3RlID0gYXMubWF0cml4KG1hdFNERCksIHNjYWxlID0gIm5vbmUiLCBkZW5kcm9ncmFtID0gInJvdyIsIGNvbG9ycyA9ICJCbHVlcyIpICMsIHRoZW1lID0gImRhcmsiKQpgYGAKCiMgVGltZSBzZXJpZXMgYW5hbHlzaXMKCkluIHRoaXMgc2VjdGlvbiB3ZSBkbyBzaW1wbGUgImZvcmVjYXN0aW5nIiAobm90IGEgc2VyaW91cyBhdHRlbXB0KS4KCk1ha2UgdGltZSBzZXJpZXMgZGF0YSBmcmFtZSBpbiBsb25nIGZvcm06CgpgYGB7cn0KZGZRdWVyeSA8LSAKICBkZk5ZRGF0YVN0YXRlcyAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KCBEYXRlLCBEYXRlT2JqZWN0ICkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2VfYXQoIC52YXJzID0gYygiQ2FzZXMiLCAiRGVhdGhzIiksIC5mdW5zID0gc3VtICkKZGZRdWVyeUxvbmdGb3JtIDwtIHRpZHlyOjpwaXZvdF9sb25nZXIoIGRmUXVlcnksIGNvbHMgPSBjKCJDYXNlcyIsICJEZWF0aHMiKSwgbmFtZXNfdG8gPSAiVmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAiVmFsdWUiKQpoZWFkKGRmUXVlcnlMb25nRm9ybSkKYGBgCgpQbG90IHRoZSB0aW1lIHNlcmllczoKCmBgYHtyfQpnZ3Bsb3QoZGZRdWVyeUxvbmdGb3JtKSArCiAgZ2VvbV9saW5lKCBhZXMoIHggPSBEYXRlT2JqZWN0LCB5ID0gbG9nMTAoVmFsdWUpICkgKSArCiAgZmFjZXRfd3JhcCggflZhcmlhYmxlLCBuY29sID0gMSApCmBgYAoKIyMgQ2FzZXMKCkZpdCB1c2luZyBBUklNQToKCmBgYHtyfQpmaXQgPC0gZm9yZWNhc3Q6OmF1dG8uYXJpbWEoIGRmUXVlcnkkQ2FzZXMgKQpmaXQKYGBgCgpQbG90ICJmb3JlY2FzdCI6CgpgYGB7cn0KcGxvdCggZm9yZWNhc3Q6OmZvcmVjYXN0KGZpdCwgaCA9IDIwKSApCmdyaWQobnggPSBOVUxMLCBueSA9IE5VTEwsIGNvbCA9ICJsaWdodGdyYXkiLCBsdHkgPSAiZG90dGVkIikKYGBgCgojIyBEZWF0aHMKCkZpdCB3aXRoIEFSSU1BOgoKYGBge3J9CmZpdCA8LSBmb3JlY2FzdDo6YXV0by5hcmltYSggZGZRdWVyeSREZWF0aHMgKQpmaXQKYGBgCgpQbG90ICJmb3JlY2FzdCI6CgpgYGB7cn0KcGxvdCggZm9yZWNhc3Q6OmZvcmVjYXN0KGZpdCwgaCA9IDIwKSApCmdyaWQobnggPSBOVUxMLCBueSA9IE5VTEwsIGNvbCA9ICJsaWdodGdyYXkiLCBsdHkgPSAiZG90dGVkIikKYGBgCgojIEZsdWN0dWF0aW9ucwoKV2Ugd2FudCB0byBzZWUgZG9lcyB0aGUgdGltZSBzZXJpZXMgZGF0YSBoYXZlIGZsdWN0dWF0aW9ucyBhcm91bmQgaXRzIHRyZW5kcyBhbmQgZXN0aW1hdGUgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgdGhvc2UgZmx1Y3R1YXRpb25zLiAKKEtub3dpbmcgdGhvc2UgZGlzdHJpYnV0aW9ucyBzb21lIGZ1cnRoZXIgc3R1ZGllcyBjYW4gYmUgZG9uZS4pCgpUaGlzIGNhbiBiZSBlZmZpY2llbnRseSB1c2luZyB0aGUgc29mdHdhcmUgbW9uYWQgYFFSTW9uYCwgW0FBcDIsIEFBMV0uIEhlcmUgd2UgbG9hZCB0aGUgYFFSTW9uYCBwYWNrYWdlOgoKYGBge3J9CiNkZXZ0b29sczo6aW5zdGFsbF9naXRodWIocmVwbyA9ICJhbnRvbm9uY3ViZS9RUk1vbi1SIikKbGlicmFyeShRUk1vbikKYGBgCgojIyBGbHVjdHVhdGlvbnMgcHJlc2VuY2UKCkhlcmUgd2UgcGxvdCB0aGUgY29uc2VjdXRpdmUgZGlmZmVyZW5jZXMgb2YgdGhlIGNhc2VzIGFuZCBkZWF0aHM6CgpgYGB7cn0KZGZRdWVyeUxvbmdGb3JtIDwtIAogIGRmUXVlcnlMb25nRm9ybSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KCBWYXJpYWJsZSApICU+JSAKICBkcGx5cjo6YXJyYW5nZSggRGF0ZU9iamVjdCApICU+JSAKICBkcGx5cjo6bXV0YXRlKCBEaWZmZXJlbmNlID0gYygwLCBkaWZmKFZhbHVlKSApICkgJT4lIAogIGRwbHlyOjp1bmdyb3VwKCkKZ2dwbG90KGRmUXVlcnlMb25nRm9ybSkgKwogIGdlb21fbGluZSggYWVzKCB4ID0gRGF0ZU9iamVjdCwgeSA9IERpZmZlcmVuY2UgKSApICsKICBmYWNldF93cmFwKCB+VmFyaWFibGUsIG5jb2wgPSAxLCBzY2FsZXMgPSAiZnJlZV95IiApCmBgYAoKRnJvbSB0aGUgcGxvdHMgd2Ugc2VlIHRoYXQgdGltZSBzZXJpZXMgYXJlIG5vdCBtb25vdG9uaWNhbGx5IGluY3JlYXNpbmcsIGFuZCB0aGVyZSBhcmUgbm9uLXRyaXZpYWwgZmx1Y3R1YXRpb25zIGluIHRoZSBkYXRhLgoKCiMjIEFic29sdXRlIGFuZCByZWxhdGl2ZSBlcnJvcnMgZGlzdHJpYnV0aW9ucwoKSGVyZSB3ZSB0YWtlIGludGVyZXN0aW5nIHBhcnQgb2YgdGhlIGNhc2VzIGRhdGE6CgpgYGB7cn0KZGZRdWVyeUxvbmdGb3JtMiA8LSAKICBkZlF1ZXJ5TG9uZ0Zvcm0gJT4lIAogIGRwbHlyOjpmaWx0ZXIoIGRpZmZ0aW1lKCBEYXRlT2JqZWN0LCBhcy5QT1NJWGN0KCIyMDIwLTA1LTAxIikpID49IDAgKSAlPiUgCiAgZHBseXI6Om11dGF0ZSggUmVncmVzc29yID0gYXMubnVtZXJpYyhEYXRlT2JqZWN0LCBvcmlnaW4gPSAiMTkwMC0wMS0wMSIpICkKYGBgCgoKSGVyZSB3ZSBzcGVjaWZ5IGEgYFFSTW9uYCB3b3JrZmxvdyB0aGF0IHJlc2NhbGVzIHRoZSBkYXRhLCAKZml0cyBhIEItc3BsaW5lIGN1cnZlIHRvIGdldCB0aGUgdHJlbmQsIAphbmQgZmluZHMgdGhlIGFic29sdXRlIGFuZCByZWxhdGl2ZSBlcnJvcnMgKHJlc2lkdWFscywgZmx1Y3R1YXRpb25zKSBhcm91bmQgdGhhdCB0cmVuZDoKCmBgYHtyfQpxck9iaiA8LSAKICBRUk1vblVuaXQoZGZRdWVyeUxvbmdGb3JtMiAlPiUgZHBseXI6OmZpbHRlciggVmFyaWFibGUgPT0gIkNhc2VzIikgJT4lIGRwbHlyOjpzZWxlY3QoIFJlZ3Jlc3NvciwgVmFsdWUpICkgJT4lIAogIFFSTW9uUmVzY2FsZShyZWdyZXNzb3JBeGlzUSA9IEYsIHZhbHVlQXhpc1EgPSBUKSAlPiUgCiAgUVJNb25FY2hvRGF0YVN1bW1hcnkgJT4lIAogIFFSTW9uUXVhbnRpbGVSZWdyZXNzaW9uKCBkZiA9IDE2LCBwcm9iYWJpbGl0aWVzID0gMC41ICkKYGBgCgoKSGVyZSB3ZSBwbG90IHRoZSBmaXQ6CgpgYGB7cn0KcXJPYmogPC0gcXJPYmogJT4lIFFSTW9uUGxvdChkYXRlUGxvdFEgPSBUKQpgYGAKCkhlcmUgd2UgcGxvdCBhYnNvbHV0ZSBlcnJvcnM6CgpgYGB7cn0KcXJPYmogPC0gcXJPYmogJT4lIFFSTW9uRXJyb3JzUGxvdChyZWxhdGl2ZUVycm9yc1EgPSBGLCBkYXRlUGxvdFEgPSBUKQpgYGAKCkhlcmUgaXMgdGhlIHN1bW1hcnk6CgpgYGB7cn0Kc3VtbWFyeSggKHFyT2JqICU+JSBRUk1vbkVycm9ycyhyZWxhdGl2ZUVycm9yc1EgPSBGKSAlPiUgUVJNb25UYWtlVmFsdWUpW1sxXV0kRXJyb3IgKQpgYGAKCgpIZXJlIHdlIHBsb3QgcmVsYXRpdmUgZXJyb3JzOgoKYGBge3J9CnFyT2JqIDwtIHFyT2JqICU+JSBRUk1vbkVycm9yc1Bsb3QocmVsYXRpdmVFcnJvcnNRID0gVCwgZGF0ZVBsb3RRID0gVCkKYGBgCgpIZXJlIGlzIHRoZSBzdW1tYXJ5OgoKYGBge3J9CnN1bW1hcnkoIChxck9iaiAlPiUgUVJNb25FcnJvcnMocmVsYXRpdmVFcnJvcnNRID0gVCkgJT4lIFFSTW9uVGFrZVZhbHVlKVtbMV1dJEVycm9yICkKYGBgCgoKIyBSZWZlcmVjZXMKCltOWVQxXSBUaGUgTmV3IFlvcmsgVGltZXMsIFtDb3JvbmF2aXJ1cyAoQ292aWQtMTkpIERhdGEgaW4gdGhlIFVuaXRlZCBTdGF0ZXNdKGh0dHBzOi8vZ2l0aHViLmNvbS9ueXRpbWVzL2NvdmlkLTE5LWRhdGEpLCAoMjAyMCksIEdpdEh1Yi4KCltXUkkxXSBXb2xmcmFtIFJlc2VhcmNoIEluYy4sIFtVU0EgY291bnR5IHJlY29yZHNdKGh0dHBzOi8vZ2l0aHViLmNvbS9hbnRvbm9uY3ViZS9TeXN0ZW1Nb2RlbGluZy9ibG9iL21hc3Rlci9EYXRhL2RmVVNBQ291bnR5UmVjb3Jkcy5jc3YpLCAoMjAyMCksIFtTeXN0ZW0gTW9kZWxpbmcgYXQgR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vYW50b25vbmN1YmUvU3lzdGVtTW9kZWxpbmcpLgoKW0pIMV0gQ1NTRSBhdCBKb2hucyBIb3BraW5zIFVuaXZlcnNpdHksIFtDT1ZJRC0xOV0oaHR0cHM6Ly9naXRodWIuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5KSwgKDIwMjApLCBHaXRIdWIuCgpbVksxXSBWaXRhbGl5IEthdXJvdiwgW1Jlc291cmNlcyBGb3IgTm92ZWwgQ29yb25hdmlydXMgQ09WSUQtMTldKGh0dHBzOi8vY29tbXVuaXR5LndvbGZyYW0uY29tL2dyb3Vwcy8tL20vdC8xODcyNjA4KSwgKDIwMjApLCBbY29tbXVuaXR5LndvbGZyYW0uY29tXShodHRwczovL2NvbW11bml0eS53b2xmcmFtLmNvbSkuCgpbQUExXSBBbnRvbiBBbnRvbm92LCBbIkEgbW9uYWQgZm9yIFF1YW50aWxlIFJlZ3Jlc3Npb24gd29ya2Zsb3dzIl0oaHR0cHM6Ly9tYXRoZW1hdGljYWZvcnByZWRpY3Rpb24ud29yZHByZXNzLmNvbS8yMDE4LzA4LzAxL2EtbW9uYWQtZm9yLXF1YW50aWxlLXJlZ3Jlc3Npb24td29ya2Zsb3dzLyksICgyMDE4KSwgYXQgW01hdGhlbWF0aWNhRm9yUHJlZGljdGlvbiBXb3JkUHJlc3NdKGh0dHBzOi8vbWF0aGVtYXRpY2Fmb3JwcmVkaWN0aW9uLndvcmRwcmVzcy5jb20pLgoKW0FBcDFdIEFudG9uIEFudG9ub3YsIFtIZWF0bWFwIHBsb3QgTWF0aGVtYXRpY2EgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2FudG9ub25jdWJlL01hdGhlbWF0aWNhRm9yUHJlZGljdGlvbi9ibG9iL21hc3Rlci9NaXNjL0hlYXRtYXBQbG90Lm0pLCAoMjAxOCksIFtNYXRoZW1hdGljYUZvclByZWRpY2l0b24gYXQgR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vYW50b25vbmN1YmUvTWF0aGVtYXRpY2FGb3JQcmVkaWN0aW9uKS4KCltBQXAyXSBBbnRvbiBBbnRvbm92LCBbTW9uYWRpYyBRdWFudGlsZSBSZWdyZXNzaW9uIE1hdGhlbWF0aWNhIHBhY2thZ2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9hbnRvbm9uY3ViZS9NYXRoZW1hdGljYUZvclByZWRpY3Rpb24vYmxvYi9tYXN0ZXIvTW9uYWRpY1Byb2dyYW1taW5nL01vbmFkaWNRdWFudGlsZVJlZ3Jlc3Npb24ubSksICgyMDE4KSwgW01hdGhlbWF0aWNhRm9yUHJlZGljaXRvbiBhdCBHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9hbnRvbm9uY3ViZS9NYXRoZW1hdGljYUZvclByZWRpY3Rpb24pLgo=